#include "Renderer.h"
#include "..\content\Material.h"
#include "GraphicsDevice.h"
#include "..\utils\Log.h"
#include "Camera.h"
#include "Light.h"
#include "RenderStage.h"
#include "..\nodes\ScreenOverlayNode.h"
#include "..\utils\Profiler.h"
#include "..\game\World.h"
#include "..\lib\container\List.h"

namespace ds {

//-----------------------------------------------
// The scene manager implementation
//-----------------------------------------------
Renderer::Renderer(HWND hWnd,const Settings& settings) {
	LOG(logINFO) << "RenderDevice::RenderDevice";
	// create the internal device
	device = new GraphicsDevice(hWnd,settings);	
	//Material* m_DefaultMaterial = new Material("Default");
	//gEngine->assetManager->add("DefaultMaterial",m_DefaultMaterial);
	// the camera
    float aspect = (float)settings.width / (float)settings.height;
	m_Camera = new Camera();
	m_Camera->setLens(D3DX_PI * 0.25f, aspect, 0.1f, 1000.0f);
	device->get()->SetTransform( D3DTS_PROJECTION, &m_Camera->getProjectionMatrix());	
	m_ClearColor = Color(0.0f,0.0f,0.0f,1.0f);
	setRenderState(D3DRS_LIGHTING,FALSE);
	setRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
	setRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
	setRenderState(D3DRS_ZENABLE,true);
	setRenderState(D3DRS_ZWRITEENABLE, TRUE);
	setRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL);
	setRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD );  
    setRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB( 80, 80, 80 ) );
	mode = settings.mode;
	m_ClearColor = settings.clearColor;
	getRenderStates();
	m_RenderTarget = 0;
	m_OverlayNode = 0;
	m_PostProcessing = settings.postProcessing;	
	prepareDefaultRendering();
	//gEngine->assetManager->add("DefaultMaterial",new Material("DefaultMaterial"));
}

void Renderer::prepareDefaultRendering() {
	m_RenderStages.append(new BackgroundRenderStage());
	m_RenderStages.append(new GeometryRenderStage());
	m_RenderStages.append(new AlphaRenderStage());
	m_RenderStages.append(new OrthogonalRenderStage());
}

Renderer::~Renderer(void) {	
	LOG(logINFO) << "destructing Renderer";	
	m_RenderStages.deleteContents();
	delete device;	
	delete m_Camera;
}

bool Renderer::Reset() {
	return true;
}

bool Renderer::beginRendering(const Color& clearColor) {		
	setRenderState( D3DRS_LIGHTING,false);	
	// Clear the backbuffer to a blue color
    HR(device->get()->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, clearColor, 1.0f, 0 ));	
    // Begin the scene
    if( SUCCEEDED( device->get()->BeginScene() ) ) {		
		rendering = true;		
		return true;
	}
	else {
		LOG(logERROR) << "cannot begin scene";
	}
	return false;
}

void Renderer::render() {
	PR_START("RENDERER_R");
	bool rt = false;		
	//List<Node*> list;		
	list.clear();
	// handle render targets
	PR_START("ACTIVE_NODES")
	if ( m_Assignments.num() > 0 ) {
		handleRenderTargets();
		gEngine->getWorld().getActiveNodes(list,WL_FORWARD);
	}	
	else {
		gEngine->getWorld().getActiveNodes(list,WL_NONE);
	}	
	PR_END("ACTIVE_NODES")
	if ( m_PostProcessing ) {				
		PR_START("RT_START");
		m_RenderTarget->saveBackBuffer();
		m_RenderTarget->setTargets();
		beginRendering(m_ClearColor);		
		setupMatrices();
		for ( size_t i = 0; i < m_RenderStages.num(); ++i ) {
			IRenderStage* stage = m_RenderStages[i];
			PR_START(stage->getName().c_str());
			stage->render(this,list,true);
			PR_END(stage->getName().c_str());
		}		
		device->get()->EndScene();
		m_RenderTarget->restoreBackBuffer();	
		PR_END("RT_START");
		beginRendering(m_ClearColor);		
		setupMatrices();
		PR_START("RT_POST");
		IRenderStage* stage = m_RenderStages[2];
		set2DCameraOn();
		setRenderState(D3DRS_ALPHABLENDENABLE,true);
		setRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE );
		setRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
		stage->renderNode(this,m_OverlayNode);		
		PR_END("RT_POST");
	}	
	else {
		beginRendering(m_ClearColor);
		setupMatrices();	
		for ( size_t i = 0; i < m_RenderStages.num(); ++i ) {		
			IRenderStage* stage = m_RenderStages[i];
			PR_START(stage->getName().c_str());
			stage->render(this,list,false);	
			PR_END(stage->getName().c_str());
		}
	}
	//for ( size_t i = 0; i < list.size();++i ) {
		//list[i]->postRendering();
	//}
	PR_END("RENDERER_R");	
}

void Renderer::endRendering() {
	rendering = false;
	device->get()->EndScene();
    device->get()->Present( NULL, NULL, NULL, NULL );
}

void Renderer::init() {
	LOG(logINFO) << "Renderer - init";	
	if ( m_PostProcessing ) {
		// create the render target
		LOG(logINFO) << "creating render target";
		m_RenderTarget = new RenderTarget();
		m_RenderTarget->create(device,0);
		ResourceHandle rh = gEngine->getResourceManager().findByName("RT0",RS_RENDERTARGET);
		gEngine->getResourceManager().createMaterial("RTMaterial",rh,true);
		m_OverlayNode = new ScreenOverlayNode("RTMaterial");
	}	
}

void Renderer::setTransformations() {
	HR(device->get()->SetTransform( D3DTS_WORLD, &m_World ));  
	HR(device->get()->SetTransform( D3DTS_VIEW, &m_Camera->getViewMatrix()));
	HR(device->get()->SetTransform( D3DTS_PROJECTION, &m_Camera->getProjectionMatrix() ));
	matWorldViewProj = m_World * m_Camera->getViewMatrix() * m_Camera->getProjectionMatrix();
}
//-----------------------------------------------------------------------------
// Name: SetupMatrices()
// Desc: Sets up the world, view, and projection transform Matrices.
//-----------------------------------------------------------------------------
VOID Renderer::setupMatrices() {  
	m_RenderMode = RM_3D;
	D3DXMatrixIdentity(&m_World);    
	setTransformations();
	setRenderState(D3DRS_ZENABLE, TRUE);
}

//-----------------------------------------------------------------------------
// Set2DCameraOn: go into 2D mode
//-----------------------------------------------------------------------------
void Renderer::set2DCameraOn() {
	// check if we are already in 2D mode
	if ( m_RenderMode != RM_2D ) {
		m_RenderMode = RM_2D;
		m_Camera->setOrthogonal();
		setTransformations();
		setRenderState(D3DRS_CULLMODE,D3DCULL_NONE);
		setRenderState(D3DRS_ZENABLE, FALSE);
	}
}


//-----------------------------------------------------------------------------
// Set2DCameraOff: exit 2D mode
//-----------------------------------------------------------------------------
void Renderer::set2DCameraOff() {
	if ( m_RenderMode == RM_2D ) {
		m_RenderMode = RM_3D;
		m_Camera->restore();		
		setTransformations();		
		setRenderState(D3DRS_ZENABLE, TRUE);		
	}
}

//-----------------------------------------------------------------------------
// Set the world matrix based on the node transformation
// and updates the WorldViewProjection matrix ready to use
//
// [in] Node* the actual node that is currently rendered
//-----------------------------------------------------------------------------
void Renderer::applyTransformation(Node* node) {
	node->getTransformation(&m_World);	
	device->get()->SetTransform(D3DTS_WORLD,&m_World);		
	matWorldViewProj = m_World * m_Camera->getViewMatrix() * m_Camera->getProjectionMatrix();
}

Camera* Renderer::getCamera() {
	return m_Camera;
}

void Renderer::getRenderStates() {
	//DWORD value;
	for ( DWORD rs = 7 ; rs < 209; rs++) {
		D3DRENDERSTATETYPE type = (D3DRENDERSTATETYPE)(rs);
		/*		
		device->get()->GetRenderState(type,&value);
		m_RenderStates[type] = value;
		*/
		m_RenderStates[type] = -1;
	}
}

void Renderer::setRenderState(D3DRENDERSTATETYPE rs,DWORD value) {
	DWORD cachedValue = m_RenderStates[rs];
	if ( cachedValue == -1 || cachedValue != value ) {
		m_RenderStates[rs] = value;
		device->get()->SetRenderState(rs,value);
	}
}

DWORD Renderer::getRenderState(D3DRENDERSTATETYPE rs) {
	DWORD cachedValue = m_RenderStates[rs];
	if ( cachedValue == -1 ) {
		device->get()->GetRenderState(rs,&cachedValue);
	}
	return cachedValue;
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
void Renderer::assignRenderTarget(ResourceHandle rh,WorldLayer layer) {
	bool found = false;
	for ( size_t i = 0; i < m_Assignments.num();++i ) {
		RTAssignment* rta = &m_Assignments[i];
		if ( rta->rtHandle == rh ) {
			rta->layers.push_back(layer);
			found = true;
		}
	}
	if ( !found ) {
		int cnt = m_Assignments.num();
		RTAssignment rta;
		rta.rtHandle = rh;
		rta.layers.push_back(layer);
		m_Assignments.append(rta);
	}
	gEngine->getWorld().setMask(layer,WL_RENDERTARGET);
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
void Renderer::handleRenderTargets() {
	for ( size_t i = 0; i < m_Assignments.num(); ++i ) {
		PR_START("RENDER_TO_TARGET");
		RTAssignment* rta = &m_Assignments[i];
		List<Node*> list;
		gEngine->getWorld().getActiveNodes(list,rta->layers,WL_RENDERTARGET);
		RenderTarget* rt = gEngine->getResourceManager().getRenderTarget(rta->rtHandle);		
		rt->saveBackBuffer();
		rt->setTargets();
		beginRendering(m_ClearColor);		
		setupMatrices();
		for ( size_t i = 0; i < m_RenderStages.num(); ++i ) {
			IRenderStage* stage = m_RenderStages[i];
			PR_START(stage->getName().c_str());
			stage->render(this,list,true);
			PR_END(stage->getName().c_str());
		}		
		device->get()->EndScene();
		rt->restoreBackBuffer();	
		PR_END("RENDER_TO_TARGET");		
	}
}

};

